//===--- ConstantBuilder.h - IR generation for constant structs -*- C++ -*-===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
//  This file implements IR generation of constant packed LLVM structs.
//===----------------------------------------------------------------------===//

#ifndef POLARPHP_IRGEN_INTERNAL_CONSTANT_BUILDER_H
#define POLARPHP_IRGEN_INTERNAL_CONSTANT_BUILDER_H

#include "polarphp/abi/MetadataValues.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/DerivedTypes.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Instructions.h"
#include "clang/CodeGen/ConstantInitBuilder.h"

#include "polarphp/irgen/internal/Address.h"
#include "polarphp/irgen/internal/IRGenModule.h"
#include "polarphp/irgen/internal/IRGenFunction.h"

namespace polar::irgen {

class ConstantAggregateBuilderBase;
class ConstantStructBuilder;
class ConstantArrayBuilder;
class ConstantInitBuilder;

struct ConstantInitBuilderTraits {
   using InitBuilder = ConstantInitBuilder;
   using AggregateBuilderBase = ConstantAggregateBuilderBase;
   using ArrayBuilder = ConstantArrayBuilder;
   using StructBuilder = ConstantStructBuilder;
};

/// A Swift customization of Clang's ConstantInitBuilder.
class ConstantInitBuilder
   : public clang::CodeGen::ConstantInitBuilderTemplateBase<
      ConstantInitBuilderTraits> {
public:
   IRGenModule &IGM;
   ConstantInitBuilder(IRGenModule &IGM)
      : ConstantInitBuilderTemplateBase(IGM.getClangCGM()),
        IGM(IGM) {}
};

class ConstantAggregateBuilderBase
   : public clang::CodeGen::ConstantAggregateBuilderBase {
   using super = clang::CodeGen::ConstantAggregateBuilderBase;
protected:
   ConstantAggregateBuilderBase(ConstantInitBuilder &builder,
                                ConstantAggregateBuilderBase *parent)
      : super(builder, parent) {}

   ConstantInitBuilder &getBuilder() const {
      return static_cast<ConstantInitBuilder&>(Builder);
   }
   IRGenModule &IGM() const { return getBuilder().IGM; }

public:
   void addInt16(uint16_t value) {
      addInt(IGM().Int16Ty, value);
   }

   void addInt32(uint32_t value) {
      addInt(IGM().Int32Ty, value);
   }

   void addRelativeAddressOrNull(llvm::Constant *target) {
      if (target) {
         addRelativeAddress(target);
      } else {
         addInt(IGM().RelativeAddressTy, 0);
      }
   }

   void addRelativeAddress(llvm::Constant *target) {
      assert(!isa<llvm::ConstantPointerNull>(target));
      addRelativeOffset(IGM().RelativeAddressTy, target);
   }

   /// Add a tagged relative reference to the given address.  The direct
   /// target must be defined within the current image, but it might be
   /// a "GOT-equivalent", i.e. a pointer to an external object; if so,
   /// set the low bit of the offset to indicate that this is true.
   void addRelativeAddress(ConstantReference reference) {
      addTaggedRelativeOffset(IGM().RelativeAddressTy,
                              reference.getValue(),
                              unsigned(reference.isIndirect()));
   }

   /// Add an indirect relative reference to the given address.
   /// The target must be a "GOT-equivalent", i.e. a pointer to an
   /// external object.
   void addIndirectRelativeAddress(ConstantReference reference) {
      assert(reference.isIndirect());
      addRelativeOffset(IGM().RelativeAddressTy,
                        reference.getValue());
   }

   Size getNextOffsetFromGlobal() const {
      return Size(super::getNextOffsetFromGlobal().getQuantity());
   }

   void addAlignmentPadding(Alignment align) {
      auto misalignment = getNextOffsetFromGlobal() % IGM().getPointerAlignment();
      if (misalignment != Size(0))
         add(llvm::ConstantAggregateZero::get(
            llvm::ArrayType::get(IGM().Int8Ty,
                                 align.getValue() - misalignment.getValue())));
   }
};

class ConstantArrayBuilder
   : public clang::CodeGen::ConstantArrayBuilderTemplateBase<
      ConstantInitBuilderTraits> {
private:
   llvm::Type *EltTy;

public:
   ConstantArrayBuilder(InitBuilder &builder,
                        AggregateBuilderBase *parent,
                        llvm::Type *eltTy)
      : ConstantArrayBuilderTemplateBase(builder, parent, eltTy), EltTy(eltTy) {}

   void addAlignmentPadding(Alignment align) {
      auto misalignment = getNextOffsetFromGlobal() % align;
      if (misalignment == Size(0))
         return;

      auto eltSize = IGM().DataLayout.getTypeStoreSize(EltTy);
      assert(misalignment.getValue() % eltSize == 0);

      for (unsigned i = 0, n = misalignment.getValue() / eltSize; i != n; ++i)
         add(llvm::Constant::getNullValue(EltTy));
   }
};

class ConstantStructBuilder
   : public clang::CodeGen::ConstantStructBuilderTemplateBase<
      ConstantInitBuilderTraits> {
public:
   template <class... As>
   ConstantStructBuilder(As &&... args)
      : ConstantStructBuilderTemplateBase(std::forward<As>(args)...) {}
};

} // end namespace polar::irgen

#endif // POLARPHP_IRGEN_INTERNAL_CONSTANT_BUILDER_H